pcall(require, 'luarocks.loader')
local gears = require('gears')
local awful = require('awful')
require('awful.autofocus')
local wibox = require('wibox')
local beautiful = require('beautiful')
local naughty = require('naughty')
local menubar = require('menubar')
local hotkeys_popup = require('awful.hotkeys_popup')
require('awful.hotkeys_popup.keys')

local acname = 'ACAD'
local batname = 'BATT'

if awesome.startup_errors then
    naughty.notify({title = 'Oops, there were errors during startup!',
        preset=naughty.config.presets.critical, text=awesome.startup_errors})
end

do
    local in_error = false
    awesome.connect_signal('debug::error', function (err)
        if in_error then return end
        in_error = true

        naughty.notify({title='Oops, an error happened!', text=tostring(err),
            preset=naughty.config.presets.critical})
        in_error = false
    end)
end

beautiful.init(gears.filesystem.get_configuration_dir()..'theme.lua')

local terminal = 'alacritty'
local modkey = 'Mod4'

awful.layout.layouts = {
    awful.layout.suit.tile.left,
    awful.layout.suit.tile,
    awful.layout.suit.fair,
    awful.layout.suit.fair.horizontal,
    awful.layout.suit.magnifier,
    awful.layout.suit.floating,
}

menubar.utils.terminal = terminal

local keyboardlayout = awful.widget.keyboardlayout()

local textclock = wibox.widget.textclock()
local taglist_buttons = gears.table.join(
    awful.button({}, 1, function(t) t:view_only() end),
    awful.button({modkey}, 1, function(t)
        if client.focus then client.focus:move_to_tag(t) end
    end),
    awful.button({}, 3, awful.tag.viewtoggle),
    awful.button({modkey}, 3, function(t)
        if client.focus then client.focus:toggle_tag(t) end
    end),
    awful.button({}, 4, function(t) awful.tag.viewnext(t.screen) end),
    awful.button({}, 5, function(t) awful.tag.viewprev(t.screen) end))

local tasklist_buttons = gears.table.join(
     awful.button({}, 1, function(c)
         if c==client.focus then c.minimized = true else
             c:emit_signal('request::activate', 'tasklist', {raise=true})
         end
     end),
     awful.button({}, 3, function()
         awful.menu.client_list({theme={width=250}})
     end),
     awful.button({}, 4, function() awful.client.focus.byidx(1) end),
     awful.button({}, 5, function() awful.client.focus.byidx(-1) end))

-- @DOC_WALLPAPER@
local function set_wallpaper(s)
    -- Wallpaper
    if beautiful.wallpaper then
        local wallpaper = beautiful.wallpaper
        -- If wallpaper is a function, call it with the screen
        if type(wallpaper) == 'function' then
            wallpaper = wallpaper(s)
        end
        gears.wallpaper.maximized(wallpaper, s, true)
    end
end

local function gradient(steps)
    local pivots, min, max = {}, nil, nil
    for i, step in pairs(steps) do
        local at, color = table.unpack(step)
        min = min==nil and at or math.min(min, at)
        max = max==nil and at or math.max(max, at)
        pivots[i] = {at, {gears.color.parse_color(color)}}
    end
    ---Transform color table (array) to its hex representation
    ---@param t Color
    ---@return color_string
    ----
    ---@alias color_string string
    ---@alias Color table
    local function to_hex(t)
        local c = {}
        local e = ''
        for i, v in ipairs(t) do
            e = string.format('%s%f %f\n', e, i, v)
            c[i] = math.min(255, math.floor(v*255))
        end
        return ("#%02x%02x%02x%02x"):format(table.unpack(c))
    end
    ---Compute the color for the coordinate.
    ---@param value number Coordinate to compute against.
    ---@return table
    local function compute_color(value)
        if value<=min then
            return pivots[1][2]
        elseif value>=max then
            return pivots[#pivots][2]
        end
        local from, to, stop = pivots[1], pivots[2], 3
        while value>to[1] do
            from = to
            to = pivots[stop]
            stop = stop+1
        end
        local res = {}
        local to_perc = (value-from[1])/(to[1]-from[1])
        local from_perc = 1-to_perc
        for i=1,4 do
            res[i] = to[2][i]*to_perc+from[2][i]*from_perc
        end
        return res
    end
    return function(value) return to_hex(compute_color(value)) end
end

local function create_battery()
    local bat_low, bat_mid, bat_high = 15, 50, 85
    local bat_gradient = gradient{
        {bat_low, beautiful.battery_red},
        {bat_mid, beautiful.battery_yellow},
        {bat_high, beautiful.battery_green},
    }
    local bat_bg_gradient = gradient{
        {bat_low, beautiful.battery_red_bg},
        {bat_mid, beautiful.battery_yellow_bg},
        {bat_high, beautiful.battery_green_bg},
    }
    local label_gradient = gradient{
        {bat_low, beautiful.bg_normal},
        {bat_high, beautiful.wibar_border_color},
    }
    local level = 0
    local charging = false
    local widget = wibox.widget{
        layout = wibox.layout.stack,
        {
            id = 'battery_bar',
            value = level,
            min_value = 0, max_value = 100,
            paddings = beautiful.thin_margin,
            background_color = beautiful.bg_normal,
            color = beautiful.bg_focus,
            border_width = beautiful.thin_border,
            border_color = beautiful.bg_focus,
            forced_width = beautiful.battery_width,
            widget = wibox.widget.progressbar,
            bar_shape = beautiful.shape.rounded_tiny,
            shape = beautiful.shape.rounded_small,
        },
        {
            widget = wibox.container.background,
            fg = beautiful.wibar_border_color,
            id = 'battery_text_bg',
            visible = true,
            {
                id = 'battery_text',
                text = 'N/A',
                color = beautiful.fg_focus,
                align = 'center',
                widget = wibox.widget.textbox,
                font = beautiful.battery_label_font,
            },
        },
        {
            widget = wibox.container.background,
            fg = beautiful.bg_normal,
            visible = false,
            id = 'battery_charging',
            {
                markup = '↯', font = beautiful.battery_chr_font,
                align = 'center', widget = wibox.widget.textbox,
            },
        },
        set_charging = function(self, val)
            charging = tonumber(val)==1
            self.battery_charging.visible = charging
            self.battery_text_bg.visible = not charging
            self.battery_bar.border_color = charging
                and bat_gradient(level) or bat_bg_gradient(level)
        end,
        set_level = function(self, val)
            level = tonumber(val) or 0
            local bar = self.battery_bar
            local text_bg = self.battery_text_bg
            bar.background_color = bat_bg_gradient(level)
            bar.value = level
            bar.color = bat_gradient(level)
            local label_color = label_gradient(level)
            self.battery_charging.fg = label_color
            text_bg.fg = label_color
            text_bg.battery_text.text = tostring(level)..'%'
            bar.border_color = charging
                and bat_gradient(level) or bat_bg_gradient(level)
        end,
    }
    local acfile = '/sys/class/power_supply/'..acname..'/online'
    gears.timer{
        timeout = 1, call_now = true, autostart = true,
        callback = function()
            local f = io.open(acfile, 'rb')
            if not f then return end
            widget.charging = f:read('*a')
            f:close()
        end,
    }
    local batfile = '/sys/class/power_supply/'..batname..'/capacity'
    gears.timer{
        timeout = 15, call_now = true, autostart = true,
        callback = function()
            local f = io.open(batfile, 'rb')
            if not f then return end
            widget.level = f:read('*a')
            f:close()
        end,
    }
    return {
        widget = wibox.container.background,
        fg = beautiful.wibar_border_color,
        widget,
    }
end

local battery_widget = create_battery()

-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
screen.connect_signal('property::geometry', set_wallpaper)

-- @DOC_FOR_EACH_SCREEN@
awful.screen.connect_for_each_screen(function(s)
    -- Wallpaper
    set_wallpaper(s)

    local tags = {'一', '二', '三', '四', '五', '六', '七', '八', '九', '十'}

    -- Each screen has its own tag table.
    awful.tag(tags, s, awful.layout.layouts[1])

    -- Create a promptbox for each screen
    s.mypromptbox = awful.widget.prompt()
    -- Create an imagebox widget which will contain an icon indicating which layout we're using.
    -- We need one layoutbox per screen.
    s.mylayoutbox = awful.widget.layoutbox(s)
    s.mylayoutbox:buttons(gears.table.join(
        awful.button({}, 1, function () awful.layout.inc( 1) end),
        awful.button({}, 3, function () awful.layout.inc(-1) end),
        awful.button({}, 4, function () awful.layout.inc( 1) end),
        awful.button({}, 5, function () awful.layout.inc(-1) end)))
    -- Create a taglist widget
    s.mytaglist = awful.widget.taglist {
        screen = s,
        filter = awful.widget.taglist.filter.all,
        buttons = taglist_buttons
    }

    -- Create a tasklist widget
    s.mytasklist = awful.widget.tasklist {
        screen = s,
        filter = awful.widget.tasklist.filter.currenttags,
        buttons = tasklist_buttons,
    }

    -- @DOC_WIBAR@
    -- Create the wibox
    s.statusbar = awful.wibar{
        bg = '#00000000',
        border_width = 0,
        height = beautiful.bar_height,
        margins = 10,
        position = 'bottom',
        screen = s,
        shape = beautiful.shape.rect,
    }

    s.bar_visibility = {}
    for i=1,#tags do s.bar_visibility[i] = true end

    s:connect_signal('tag::history::update', function()
        if #s.selected_tags~=1 then s.statusbar.visible = true end
        s.statusbar.visible = s.bar_visibility[s.selected_tag.index]
    end)

    local function bar_segment(t)
        t.layout = t.layout or wibox.layout.flex.horizontal
        return {
            widget = wibox.container.margin,
            margins = beautiful.thin_margin,
            {
                widget = wibox.container.background,
                bg = beautiful.wibar_bg or beautiful.bg_normal,
                shape = beautiful.wibar_shape,
                shape_border_color = beautiful.wibar_border_color,
                shape_border_width = 0,
                {
                    widget = wibox.container.margin,
                    margins = beautiful.thin_margin*2,
                    t,
                },
            },
        }
    end

    local function item_border(t)
        return {
            widget = wibox.container.background,
            shape = beautiful.shape.rounded_small,
            shape_border_color = beautiful.wibar_border_color,
            shape_border_width = beautiful.thin_border,
            t
        }
    end

    s.systray = wibox.widget.systray()

    s.statusbar:setup {
        widget = wibox.container.margin,
        bottom = beautiful.useless_margin,
        left = beautiful.useless_margin-beautiful.thin_margin,
        right = beautiful.useless_margin-beautiful.thin_margin,
        {
            layout = wibox.layout.align.horizontal,
            bar_segment{
                layout = wibox.layout.fixed.horizontal,
                s.mytaglist,
                s.mypromptbox,
            },
            bar_segment{s.mytasklist},
            {
                layout = wibox.layout.align.horizontal,
                bar_segment{
                    layout = wibox.layout.fixed.horizontal,
                    battery_widget,
                },
                bar_segment{
                    layout = wibox.layout.fixed.horizontal,
                    spacing = beautiful.light_margin,
                    item_border(keyboardlayout),
                    s.systray,
                    item_border(textclock),
                    s.mylayoutbox,
                },
            },
        }
}
end)

root.buttons(gears.table.join(
    awful.button({}, 4, awful.tag.viewnext),
    awful.button({}, 5, awful.tag.viewprev)
))

local function modify_backlight(amount)
    local op = ' -A '
    if amount < 0 then
        op = ' -U '
        amount = -amount
    end
    return function()
        awful.spawn.with_shell('light'..op..tostring(amount))
    end
end

globalkeys = gears.table.join(
    awful.key({modkey}, 's', hotkeys_popup.show_help,
        {description='show help', group='awesome'}),
    awful.key({modkey}, 'Left', awful.tag.viewprev,
        {description='view previous', group='tag'}),
    awful.key({modkey}, 'Right', awful.tag.viewnext,
        {description='view next', group='tag'}),
    awful.key({modkey}, 'Escape', awful.tag.history.restore,
        {description='go back', group='tag'}),
    awful.key({modkey}, 'j', function() awful.client.focus.byidx(1) end,
        {description='focus next by index', group='client'}),
    awful.key({modkey}, 'k', function() awful.client.focus.byidx(-1) end,
        {description='focus previous by index', group='client'}),
    awful.key({modkey, 'Shift'}, 'j', function()
        awful.client.swap.byidx(1)
    end, {description='swap with next client by index', group='client'}),
    awful.key({modkey, 'Shift'}, 'k', function()
        awful.client.swap.byidx(-1)
    end, {description='swap with previous client by index', group='client'}),
    awful.key({modkey, 'Control'}, 'j', function()
        awful.screen.focus_relative(1)
    end, {description='focus the next screen', group='screen'}),
    awful.key({modkey, 'Control'}, 'k', function()
        awful.screen.focus_relative(-1)
    end, {description='focus the previous screen', group='screen'}),
    awful.key({modkey}, 'u', awful.client.urgent.jumpto,
        {description='jump to urgent client', group='client'}),
    awful.key({modkey}, 'Tab', function()
        awful.client.focus.history.previous()
        if client.focus then client.focus:raise() end
    end, {description='go back', group='client'}),
    awful.key({}, 'XF86MonBrightnessDown', modify_backlight(-2)),
    awful.key({}, 'XF86MonBrightnessUp', modify_backlight(2)),
    awful.key({modkey}, 'Return', function() awful.spawn(terminal) end,
        {description='open a terminal', group='launcher'}),
    awful.key({modkey, 'Control'}, 'r', awesome.restart,
        {description='reload awesome', group='awesome'}),
    awful.key({modkey, 'Shift'}, 'q', awesome.quit,
        {description='quit awesome', group='awesome'}),
    awful.key({modkey}, 'l', function() awful.tag.incmwfact(0.05) end,
        {description='increase master width factor', group='layout'}),
    awful.key({modkey}, 'b', function()
        local screen = awful.screen.focused{client=true, mouse=false}
                    or awful.screen.focused{client=false, mouse=true}
        if not screen or #screen.selected_tags~=1 then return end
        local i = screen.selected_tag.index
        screen.bar_visibility[i] = not screen.bar_visibility[i]
        screen.statusbar.visible = screen.bar_visibility[i]
    end, {description='toggle statusbar visibility', group='layout'}),
    awful.key({modkey}, 'h', function() awful.tag.incmwfact(-0.05) end,
        {description='decrease master width factor', group='layout'}),
    awful.key({modkey, 'Shift'}, 'h', function()
        awful.tag.incnmaster(1, nil, true)
    end, {description='increase the number of master clients',
        group='layout'}),
    awful.key({modkey, 'Shift'}, 'l', function()
        awful.tag.incnmaster(-1, nil, true)
    end, {description='decrease the number of master clients',
        group='layout'}),
    awful.key({modkey, 'Control'}, 'h', function()
        awful.tag.incncol(1, nil, true)
    end, {description='increase the number of columns', group='layout'}),
    awful.key({modkey, 'Control'}, 'l', function()
        awful.tag.incncol(-1, nil, true)
    end, {description='decrease the number of columns', group='layout'}),
    awful.key({modkey}, 'space', function() awful.layout.inc(1) end,
        {description='select next', group='layout'}),
    awful.key({modkey, 'Shift'}, 'space', function() awful.layout.inc(-1) end,
        {description='select previous', group='layout'}),
    awful.key({modkey, 'Control'}, 'n', function()
        local c = awful.client.restore()
        if c then
            c:emit_signal('request::activate', 'key.unminimize', {raise=true})
        end
    end, {description='restore minimized', group='client'}),
    awful.key({modkey}, 'r', function()
        awful.screen.focused().mypromptbox:run()
    end, {description = 'run prompt', group = 'launcher'}),
    awful.key({ modkey }, 'x',
              function ()
                  awful.prompt.run {
                    prompt       = 'Run Lua code: ',
                    textbox      = awful.screen.focused().mypromptbox.widget,
                    exe_callback = awful.util.eval,
                    history_path = awful.util.get_cache_dir() .. '/history_eval'
                  }
              end,
              {description = 'lua execute prompt', group = 'awesome'}),
    -- Menubar
    awful.key({ modkey }, 'p', function() menubar.show() end,
              {description = 'show the menubar', group = 'launcher'})
)

-- @DOC_CLIENT_KEYBINDINGS@
clientkeys = gears.table.join(
    awful.key({ modkey,           }, 'f',
        function (c)
            c.fullscreen = not c.fullscreen
            c:raise()
        end,
        {description = 'toggle fullscreen', group = 'client'}),
    awful.key({ modkey, 'Shift'   }, 'c',      function (c) c:kill()                         end,
              {description = 'close', group = 'client'}),
    awful.key({ modkey, 'Control' }, 'space',  awful.client.floating.toggle                     ,
              {description = 'toggle floating', group = 'client'}),
    awful.key({ modkey, 'Control' }, 'Return', function (c) c:swap(awful.client.getmaster()) end,
              {description = 'move to master', group = 'client'}),
    awful.key({ modkey,           }, 'o',      function (c) c:move_to_screen()               end,
              {description = 'move to screen', group = 'client'}),
    awful.key({ modkey,           }, 't',      function (c) c.ontop = not c.ontop            end,
              {description = 'toggle keep on top', group = 'client'}),
    awful.key({ modkey,           }, 'n',
        function (c)
            -- The client currently has the input focus, so it cannot be
            -- minimized, since minimized clients can't have the focus.
            c.minimized = true
        end ,
        {description = 'minimize', group = 'client'}),
    awful.key({ modkey,           }, 'm',
        function (c)
            c.maximized = not c.maximized
            c:raise()
        end ,
        {description = '(un)maximize', group = 'client'}),
    awful.key({ modkey, 'Control' }, 'm',
        function (c)
            c.maximized_vertical = not c.maximized_vertical
            c:raise()
        end ,
        {description = '(un)maximize vertically', group = 'client'}),
    awful.key({ modkey, 'Shift'   }, 'm',
        function (c)
            c.maximized_horizontal = not c.maximized_horizontal
            c:raise()
        end ,
        {description = '(un)maximize horizontally', group = 'client'})
)

-- @DOC_NUMBER_KEYBINDINGS@
-- Bind all key numbers to tags.
-- Be careful: we use keycodes to make it work on any keyboard layout.
-- This should map on the top row of your keyboard, usually 1 to 9.
for i = 1, 10 do
    globalkeys = gears.table.join(globalkeys,
        -- View tag only.
        awful.key({ modkey }, '#' .. i + 9,
                  function ()
                        local screen = awful.screen.focused()
                        local tag = screen.tags[i]
                        if tag then
                           tag:view_only()
                        end
                  end,
                  {description = 'view tag #'..i, group = 'tag'}),
        -- Toggle tag display.
        awful.key({ modkey, 'Control' }, '#' .. i + 9,
                  function ()
                      local screen = awful.screen.focused()
                      local tag = screen.tags[i]
                      if tag then
                         awful.tag.viewtoggle(tag)
                      end
                  end,
                  {description = 'toggle tag #' .. i, group = 'tag'}),
        -- Move client to tag.
        awful.key({ modkey, 'Shift' }, '#' .. i + 9,
                  function ()
                      if client.focus then
                          local tag = client.focus.screen.tags[i]
                          if tag then
                              client.focus:move_to_tag(tag)
                          end
                     end
                  end,
                  {description = 'move focused client to tag #'..i, group = 'tag'}),
        -- Toggle tag on focused client.
        awful.key({ modkey, 'Control', 'Shift' }, '#' .. i + 9,
                  function ()
                      if client.focus then
                          local tag = client.focus.screen.tags[i]
                          if tag then
                              client.focus:toggle_tag(tag)
                          end
                      end
                  end,
                  {description = 'toggle focused client on tag #' .. i, group = 'tag'})
    )
end

-- @DOC_CLIENT_BUTTONS@
clientbuttons = gears.table.join(
    awful.button({ }, 1, function (c)
        c:emit_signal('request::activate', 'mouse_click', {raise = true})
    end),
    awful.button({ modkey }, 1, function (c)
        c:emit_signal('request::activate', 'mouse_click', {raise = true})
        awful.mouse.client.move(c)
    end),
    awful.button({ modkey }, 3, function (c)
        c:emit_signal('request::activate', 'mouse_click', {raise = true})
        awful.mouse.client.resize(c)
    end)
)

-- Set keys
root.keys(globalkeys)
-- }}}

-- {{{ Rules
-- Rules to apply to new clients (through the 'manage' signal).
-- @DOC_RULES@
awful.rules.rules = {
    -- @DOC_GLOBAL_RULE@
    -- All clients will match this rule.
    { rule = { },
      properties = { border_width = beautiful.border_width,
                     border_color = beautiful.border_normal,
                     focus = awful.client.focus.filter,
                     raise = true,
                     keys = clientkeys,
                     buttons = clientbuttons,
                     screen = awful.screen.preferred,
                     placement = awful.placement.no_overlap+awful.placement.no_offscreen
     }
    },

    -- @DOC_FLOATING_RULE@
    -- Floating clients.
    { rule_any = {
        instance = {
          'DTA',  -- Firefox addon DownThemAll.
          'copyq',  -- Includes session name in class.
          'pinentry',
        },
        class = {
          'Arandr',
          'Blueman-manager',
          'Gpick',
          'MessageWin',  -- kalarm.
          'Sxiv',
          'Tor Browser', -- Needs a fixed window size to avoid fingerprinting by screen size.
          'Wpa_gui',
          'veromix',
          'xtightvncviewer'},

        -- Note that the name property shown in xprop might be set slightly after creation of the client
        -- and the name shown there might not match defined rules here.
        name = {
          'Event Tester',  -- xev.
        },
        role = {
          'AlarmWindow',  -- Thunderbird's calendar.
          'ConfigManager',  -- Thunderbird's about:config.
          'pop-up',       -- e.g. Google Chrome's (detached) Developer Tools.
        }
      }, properties = { floating = true }},

    {rule_any={type={'normal', 'dialog'}}, properties={titlebars_enabled=true}},
}

client.connect_signal('manage', function (c)
    if awesome.startup
      and not c.size_hints.user_position
      and not c.size_hints.program_position then
        awful.placement.no_offscreen(c)
    end
    c.shape = function(cr, w, h)
        if c.fullscreen or c.maximized then
            return beautiful.shape.default(cr, w, h)
        end
        return beautiful.shape.rounded(cr, w, h)
    end
end)

-- @DOC_TITLEBARS@
-- Add a titlebar if titlebars_enabled is set to true in the rules.
client.connect_signal('request::titlebars', function(c) end)

-- Enable sloppy focus, so that focus follows mouse.
client.connect_signal('mouse::enter', function(c)
    c:emit_signal('request::activate', 'mouse_enter', {raise = false})
end)

-- @DOC_BORDER@
client.connect_signal('focus', function(c) c.border_color = beautiful.border_focus end)
client.connect_signal('unfocus', function(c) c.border_color = beautiful.border_normal end)
-- }}}
